﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using MathNet.Numerics.RootFinding;
using System.Globalization;
using AdaptiveDeNet.DifferentialEvolution.Common.Crossover;
using AdaptiveDeNet.RAND;

namespace AdaptiveDeNet.DifferentialEvolution.Common
{
    public class StrategyPool
    {
        private double n0;
        private double delta;

        private const double H = 12.0;
        private IStrategy[] pool = new IStrategy[12];
        public double[] stratPropabilities = new double[12];
        public long[] stratSuccesses = new long[12];

        public long[] numSucceses = new long[12];

        private RAND.Rand nRand;

        public StrategyPool(int _dim, int _N, double[] _lowerBound, double[] _upperBound, RAND.Rand _nRand, double n0, double delta)
        {
            this.n0 = n0;
            this.delta = delta;
            this.nRand = _nRand;

            for (int i = 0; i < 6; i++)
            {
                pool[i] = new rand1bin();
            }

            for (int i = 6; i < pool.Length; i++)
            {
                pool[i] = new rand1exp();
            }

            double[] binF = new double[2] {0.5, 0.8};
            double[] binCr = new double[3] { 0, 0.5, 1.0 };

            for (int i = 0, count = 0; i < binF.Length; i++)
            {
                for (int j = 0; j < binCr.Length; j++, count++)
                {
                    pool[count].InitParams(binF[i], binCr[j], _dim, _N, _lowerBound, _upperBound, _nRand);    
                }
            }

            double[] expF = new double[2] { 0.5, 0.8 };
            double[] expCr = GetExpCrValues(_dim);

            for (int i = 0, count = 6; i < expF.Length; i++)
            {
                for (int j = 0; j < expCr.Length; j++, count++)
                {
                    pool[count].InitParams(expF[i], expCr[j], _dim, _N, _lowerBound, _upperBound, _nRand);
                }
            }


            //Shuffle();
            ResetStratPropabilities();
            ResetStratSuccesses();
        }

        private void Shuffle()
        {
            IStrategy temp;

            for (int i = pool.Length - 1, j = 0; i > 0; i--)
            {
                j = nRand.Next(pool.Length - 1);
                temp = this.pool[j];
                this.pool[j] = this.pool[i];
                this.pool[i] = temp;
            }

        }

        public Tuple<int, Member> GetTrialMember(Member[] mainPop, int targetIndex) 
        {
            //strategy choosing - roullete

            double rd = nRand.NextDouble();
            double sum = 0;

            int strategyIndex = 0;
            int i = 0;

            do
            {
                sum += stratPropabilities[i];
                strategyIndex = i;
                i++;

            } while (sum < rd);

            //get trial

            Member trial = pool[strategyIndex].GetTrial(mainPop, targetIndex);

            return new Tuple<int, Member>(strategyIndex, trial);
        }

        public void EvalStrategyPool(int strategyIndex)
        {
            ++stratSuccesses[strategyIndex];
            ++numSucceses[strategyIndex];


            double sum1 = NjSum();

            for (int i = 0; i < stratPropabilities.Length; i++)
            {
                stratPropabilities[i] = (stratSuccesses[i] + n0) / sum1;

                if (stratPropabilities[i] < delta)
                {
                    this.ResetStratPropabilities();
                    this.ResetStratSuccesses();

                    return;
                }
            }
        }

        private double NjSum()
        {
            double sum = 0;

            for (int i = 0; i < stratSuccesses.Length; i++)
            {
                sum += (stratSuccesses[i] + n0);
            }

            return sum;
        }

        private void ResetStratSuccesses()
        {
            for (int i = 0; i < stratSuccesses.Length; i++)
            {
                stratSuccesses[i] = 1;
            }
        }

        private void ResetStratPropabilities()
        {
            for (int i = 0; i < stratPropabilities.Length; i++)
            {
                stratPropabilities[i] = 1 / H;
            }
        }

        private double[] GetExpCrValues(int _dim)
        {
            double[] CrVals = new double[3];

            double dim = (double)_dim;

            double p2 = (1 + 1 / dim) / 2.0;
            double p1 = (1 / dim + p2) / 2.0;
            double p3 = (p2 + 1) / 2.0;

            double[] pVals = new double[3];

            pVals[0] = p1;
            pVals[1] = p2;
            pVals[2] = p3;

            for (int i = 0; i < CrVals.Length; i++)
            {
                double[] polyCoeffs = new double[_dim + 1];

                polyCoeffs[_dim ] = 1;
                polyCoeffs[0] = dim * pVals[i] - 1;
                polyCoeffs[1] = -dim * pVals[i];

                CrVals[i] = MathNet.Numerics.RootFinding.Bisection.FindRoot(x => (Math.Pow(x, dim) + polyCoeffs[0] + polyCoeffs[1] * x), 0, 0.999999, 
                    1e-8, 100);  
            }

            return CrVals;
        }

        public Tuple<int, Member> GetTrialMember(Member[] mainPop, int targetIndex, RngStream rngStream)
        {
            //strategy choosing - roullete

            double rd = rngStream.randU01();
            double sum = 0;

            int strategyIndex = 0;
            int i = 0;

            do
            {
                sum += stratPropabilities[i];
                strategyIndex = i;
                i++;

            } while (sum < rd && i < stratPropabilities.Length);

            //get trial

            Member trial = pool[strategyIndex].GetTrial(mainPop, targetIndex, rngStream);

            return new Tuple<int, Member>(strategyIndex, trial);
        }
    }
}
